package com.philosophy.web.domain.generate;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Random;

/**
 * <p>
 * 通过自增序列(一定范围内随机)的方式映射短链接
 *
 * </p>
 *
 * @author philosophy
 */
@Component
public class GenerateShortUrlByIdentity extends AbstractGenerateShortUrl {

    private Long identityBegin;

    private Long identityEnd;

    private final Long interval;

    private final String shortUrlKeyCount;

    private final long[] randomCountQueue;

    /**
     * 由于泛型擦除，value使用Long类型存在Integer强制转换Long类型的错误，故使用String代替Long
     */
    @Resource
    private RedisTemplate<String, String> redisTemplate;

    @PostConstruct
    public void init() {
        obtainIdentity();
    }

    public GenerateShortUrlByIdentity() {
        interval = 10000L;
        shortUrlKeyCount = "shortUrlKeyCount";
        this.randomCountQueue = new long[Math.toIntExact(interval)];
    }

    @Override
    protected long getStrIdentity(String str) {
        if (identityBegin >= identityEnd) {
            //从全局取号器获取开始号,并设置开始和结束的号
            obtainIdentity();
        }
        return getRandom();
    }

    @Override
    protected String getSalt() {
        return "";
    }

    private long getRandom() {
        int index = Math.toIntExact((identityBegin++) % interval);
        return this.randomCountQueue[index];
    }

    /**
     * 此处仅考虑单机模式，分布式下需使用分布式锁
     */
    private synchronized void obtainIdentity() {
        String shortUrlKeyCountStr = redisTemplate.opsForValue().get(shortUrlKeyCount);
        this.identityBegin = shortUrlKeyCountStr == null ? 0 : Long.parseLong(shortUrlKeyCountStr);
        this.identityEnd = identityBegin + interval;
        initRandomCountQueue();
        redisTemplate.opsForValue().set(shortUrlKeyCount, String.valueOf(identityEnd));
    }

    /**
     * 随机打乱identityBegin到identityEnd范围的数据到randomCountQueue
     */
    private void initRandomCountQueue() {
        for (long i = identityBegin; i < identityEnd; i++) {
            this.randomCountQueue[(int) (i % interval)] = i;
        }
        shuffle(this.randomCountQueue);
    }

    private void shuffle(long[] randomCountQueue) {
        Random random = new Random();
        int size = randomCountQueue.length;
        for (int i = size; i > 1; i--) {
            swap(this.randomCountQueue, i - 1, random.nextInt(i));
        }
    }

    private void swap(long[] x, int i, int j) {
        long t = x[i];
        x[i] = x[j];
        x[j] = t;
    }
}
